package Derivation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import Basics.*;

public class ParseResultsTopDown 
{
	DeriveByProgramTopDown programDeriver;
	
	Vector<Atom> atomsToDerive = new Vector<Atom>();
	
	Map<Atom, HashSet<Body>> provenance = new HashMap<Atom, HashSet<Body>>();
	
	public ParseResultsTopDown (Rule ... irs)
	{
		this.programDeriver = new DeriveByProgramTopDown (irs);
	}
	
	
	
	public ParseResultsTopDown (Program p)
	{
		this.programDeriver = new DeriveByProgramTopDown (p);
	}
	
	
	
	public DeriveByProgramTopDown getProgramDeriver() 
	{
		return programDeriver;
	}



	public void setProgramDeriver(DeriveByProgramTopDown programDeriver) 
	{
		this.programDeriver = programDeriver;
	}



	public Vector<Atom> getAtomsToDerive() 
	{
		return atomsToDerive;
	}



	public void setAtomsToDerive(Vector<Atom> atomsToDerive) 
	{
		this.atomsToDerive = atomsToDerive;
	}



	public Map<Atom, HashSet<Body>> getProvenance() 
	{
		return provenance;
	}


	
	
	/*************************************************************************************************************/
	/** Title: ParseResults																				
	/** Description: One iteration of top down with one atom			
	/*************************************************************************************************************/
	
	public void ParseResults ()
	{
		//long startTime = System.currentTimeMillis();
		Atom instAtom = getAtomsToDerive().remove(0);
		this.programDeriver.Reset();
		this.programDeriver.GetDerivationsForAtomTopDown(instAtom);
		this.provenance.put(instAtom, this.programDeriver.getProvenanceForAtom());
		AddToAtomsNeedToBeDerived(this.programDeriver.getProvenanceForAtom());
		
		/*long endTime = System.currentTimeMillis();
		double intersectionTime = (endTime - startTime);*/
		/*if (this.atomsToDerive.size() > 20) 
		{
			System.out.println("num of atoms to derive: " + this.atomsToDerive.size());
		}*/
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: ClearProvenanceFromIrrelevantBodies																				
	/** Description: clears the prov. vector from atoms that cannot be derived		
	/*************************************************************************************************************/
	
	public void ClearProvenanceFromIrrelevantBodies()
	{
		Map<Atom, Vector<Body>> toBeRemoved = new HashMap<Atom, Vector<Body>> ();
		for (Atom key : this.provenance.keySet()) 
		{
			Vector<Body> bodies = new Vector<Body>();
			for (Body body : this.provenance.get(key)) 
			{
				for (Atom atom : body.getAtoms()) 
				{
					if (true == IsNotDerivable(atom))
					{
						bodies.add(body);
					}
				}
			}
			
			toBeRemoved.put(key, bodies);
		}
		
		for (Atom key : toBeRemoved.keySet()) 
		{
			this.provenance.get(key).removeAll(toBeRemoved.get(key));
		}
	}

	
	
	/*************************************************************************************************************/
	/** Title: HasBeenDerived																				
	/** Description: Checks if atom is already in prov. or atom is already in DB  				
	/*************************************************************************************************************/
	
	public boolean HasBeenDerived (Atom atom)
	{
		return this.provenance.containsKey(atom) || DB.getInstance().ContainedInTable(atom);
	}
	
	
	/*************************************************************************************************************/
	/** Title: IsNotDeriveble																				
	/** Description: Checks if atom cannot be derived  				
	/*************************************************************************************************************/
	
	public boolean IsNotDerivable (Atom atom)
	{
		return !DB.getInstance().ContainedInTable(atom) && atom.IsAtomRelationEdb(this.programDeriver.p);
	}
	
	
	/*************************************************************************************************************/
	/** Title: AddToAtomsNeedToBeDerived																				
	/** Description: select atoms that need to be derived and add them to vector		
	/*************************************************************************************************************/
	
	public void AddToAtomsNeedToBeDerived (HashSet<Body> bodiesOfPartlyInstRules)
	{
		for (Body body : bodiesOfPartlyInstRules) 
		{
			for (Atom atom : body.getAtoms()) 
			{
				boolean needToBeAdded = true;				
				if (true == atom.IsAtomRelationEdb(this.programDeriver.p)) //atom cannot be derived 
				{
					needToBeAdded = false;
				}
				
				if (true == HasBeenDerived(atom))
				{
					needToBeAdded = false;
				}
				
				if (true == this.atomsToDerive.contains(atom)) //atom is already in list
				{
					needToBeAdded = false;
				}
				
				if (true == needToBeAdded) 
				{
					this.atomsToDerive.add(atom);
				}
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: getRelevantProvFromAllDerivations																				
	/** Description: get the derivations relevant to derive this specific partlyInstAtom			
	/*************************************************************************************************************/
	
	public Vector<Vector<Atom>> getProv(Atom instAtom, Vector<Vector<Rule>> derivationsVec)
	{
		Vector<Vector<Atom>> prov = new Vector<Vector<Atom>>();
		for (Vector<Rule> vector : derivationsVec) //add rules that have head that match the desired atom
		{
			for (Rule rule : vector) 
			{
				prov.add(rule.getBody().getAtoms());
				//set the first element of the body with the rule used to not lose data given in DeriveByRuleTopDown
				rule.getBody().setRuleUsed(rule.getHead().getRuleUsed());
			}
		}
		
		if (DB.getInstance().ContainedInTable(instAtom)) 
		{
			Vector<Atom> fact = new Vector<Atom> ();
			fact.add(instAtom);
			prov.add(fact);
		}
		
		return prov;
	}
	

	
	/*************************************************************************************************************/
	/** Title: LeaveOnlyPossibleDerivations																				
	/** Description: clears the final prov. vector from atoms that cannot be derived		
	/*************************************************************************************************************/
	
	public void LeaveOnlyPossibleDerivations()
	{
		Map<Atom, Vector<Body>> toBeRemoved = new HashMap<Atom, Vector<Body>> ();
		int markedAtCurrentStep = 1;
		
		while (markedAtCurrentStep > 0)
		{
			markedAtCurrentStep = MarkAtomsWithStableDerivation();
		}
		
		
		for (Atom key : this.provenance.keySet()) 
		{
			Vector<Body> bodies = new Vector<Body>();
			for (Body body : this.provenance.get(key)) 
			{
				for (Atom atom : body.getAtoms()) 
				{
					if (false == this.provenance.containsKey(atom) && true == IsNotDerivable(atom))
					{
						bodies.add(body);
					}
					
					if (false == bodies.contains(body) && false == atom.isStable()) 
					{
						bodies.add(body);
					}
				}
			}
			
			toBeRemoved.put(key, bodies);
		}
		
		for (Atom key : toBeRemoved.keySet()) 
		{
			this.provenance.get(key).removeAll(toBeRemoved.get(key));
			if (this.provenance.get(key).isEmpty()) 
			{
				this.provenance.remove(key);
			}
		}
		
		UpdateDB();
	}

	
	
	/*************************************************************************************************************/
	/** Title: UpdateDB																				
	/** Description: add newly derived facts to DB			
	/*************************************************************************************************************/
	
	public void UpdateDB ()
	{
		for (Atom atom : this.provenance.keySet()) 
		{
			DB.getInstance().Update(atom);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: LeaveOnlyRelevantDerivations																				
	/** Description: clears the final prov. vector from atoms that are not used in the derivation of root		
	/*************************************************************************************************************/
	
	public void LeaveOnlyRelevantDerivations (Atom root)
	{
		List<Atom> toBeRemoved = new ArrayList<Atom>();
		MarkRelevantProv(root);
		for (Atom key : this.provenance.keySet()) 
		{
			if (false == key.isRelevantForDerivationTopDown()) 
			{
				toBeRemoved.add(key);
			}
		}
		
		for (Atom atom : toBeRemoved) 
		{
			this.provenance.remove(atom);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: IsDerivationStable																				
	/** Description: Checks if the atom can be derived from the DB and program or just has an endless cycle in the prov.		
	/*************************************************************************************************************/
	
	public void HasStableDerivation (Atom atom)
	{
		if (DB.getInstance().ContainedInTable(atom)) 
		{
			atom.setStable(true);
		}
		
		if (false == atom.isStable()) 
		{
			for (Body body : this.provenance.get(atom)) 
			{
				boolean isBodyStable = true;
				for (Atom bodyAtom : body.getAtoms()) 
				{
					if (false == bodyAtom.isStable()) 
					{
						isBodyStable = false;
					}
				}
				
				if (true == isBodyStable) //the entire vector is consisted of stable atoms 
				{
					atom.setStable(true);
					break;
				}
				
				isBodyStable = true;
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkAtomsWithStableDerivation																				
	/** Description: Checks if the atoms in prov. can be derived from the DB and program or just have an endless cycle in the prov.		
	/*************************************************************************************************************/
	
	public int MarkAtomsWithStableDerivation ()
	{
		int markedAtCurrentStep = 0;
		for (Atom key : this.provenance.keySet()) 
		{
			for (Body body : this.provenance.get(key)) 
			{
				for (Atom atom : body.getAtoms()) 
				{
					boolean wasStableBefore = atom.isStable();
					HasStableDerivation(atom);
					if (atom.isStable() != wasStableBefore) 
					{
						markedAtCurrentStep++;
					}
				}
			}
		}
		
		return markedAtCurrentStep;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkRelevantProvPerAtom																				
	/** Description: Marks all atoms that derive this root		
	/*************************************************************************************************************/
	
	public void MarkRelevantProvPerAtom (List<Atom> queue, Map<String, Atom> keyMap)
	{
		Atom toMark = queue.remove(0);
		toMark.setRelevantForDerivationTopDown(true);
		if (false == toMark.isFact() && false == keyMap.get(toMark.toString()).isRelevantForDerivationTopDown())
		{
			Atom key = keyMap.get(toMark.toString());
			key.setRelevantForDerivationTopDown(true);
			for (Body body : this.provenance.get(key)) 
			{
				for (Atom atom : body.getAtoms()) 
				{
					if (false == atom.isRelevantForDerivationTopDown()) 
					{
						queue.add(atom);
					}
				}
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkRelevantProv																				
	/** Description: removes all atoms that aren't relevant for the derivation tree of this fact		
	/*************************************************************************************************************/
	
	public void MarkRelevantProv (Atom root)
	{		
		List<Atom> queue = new ArrayList<Atom>();
		queue.add(root);
		
		Map<String, Atom> keyMap = new HashMap<String, Atom>(); 
		for (Atom key : this.provenance.keySet()) //insert all keys to a hash map for easy search 
		{
			keyMap.put(key.toString(), key);
		}
		
		while (false == queue.isEmpty())
		{
			MarkRelevantProvPerAtom(queue, keyMap);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: GetProvSize																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public int GetProvSize ()
	{
		int size = 0;
		for (HashSet<Body> set : this.provenance.values()) 
		{
			size += set.size();
		}
		
		return size;
	}
}
